{
 "cells": [
  {
   "cell_type": "code",
   "id": "initial_id",
   "metadata": {
    "collapsed": true,
    "ExecuteTime": {
     "end_time": "2025-02-21T13:08:56.840774Z",
     "start_time": "2025-02-21T13:08:51.382255Z"
    }
   },
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "    \n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=12, micro=3, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.1\n",
      "torch 2.6.0+cu118\n",
      "cuda:0\n"
     ]
    }
   ],
   "execution_count": 2
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 准备数据",
   "id": "8ec6925837c74441"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-21T13:08:56.948402Z",
     "start_time": "2025-02-21T13:08:56.841799Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.datasets import fetch_california_housing\n",
    "\n",
    "housing = fetch_california_housing(data_home='../data')\n",
    "print(type(housing))\n",
    "print(housing.DESCR)\n",
    "print(housing.data.shape)\n",
    "print(housing.target.shape)"
   ],
   "id": "5aad58f27e8d35a9",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class 'sklearn.utils._bunch.Bunch'>\n",
      ".. _california_housing_dataset:\n",
      "\n",
      "California Housing dataset\n",
      "--------------------------\n",
      "\n",
      "**Data Set Characteristics:**\n",
      "\n",
      ":Number of Instances: 20640\n",
      "\n",
      ":Number of Attributes: 8 numeric, predictive attributes and the target\n",
      "\n",
      ":Attribute Information:\n",
      "    - MedInc        median income in block group\n",
      "    - HouseAge      median house age in block group\n",
      "    - AveRooms      average number of rooms per household\n",
      "    - AveBedrms     average number of bedrooms per household\n",
      "    - Population    block group population\n",
      "    - AveOccup      average number of household members\n",
      "    - Latitude      block group latitude\n",
      "    - Longitude     block group longitude\n",
      "\n",
      ":Missing Attribute Values: None\n",
      "\n",
      "This dataset was obtained from the StatLib repository.\n",
      "https://www.dcc.fc.up.pt/~ltorgo/Regression/cal_housing.html\n",
      "\n",
      "The target variable is the median house value for California districts,\n",
      "expressed in hundreds of thousands of dollars ($100,000).\n",
      "\n",
      "This dataset was derived from the 1990 U.S. census, using one row per census\n",
      "block group. A block group is the smallest geographical unit for which the U.S.\n",
      "Census Bureau publishes sample data (a block group typically has a population\n",
      "of 600 to 3,000 people).\n",
      "\n",
      "A household is a group of people residing within a home. Since the average\n",
      "number of rooms and bedrooms in this dataset are provided per household, these\n",
      "columns may take surprisingly large values for block groups with few households\n",
      "and many empty houses, such as vacation resorts.\n",
      "\n",
      "It can be downloaded/loaded using the\n",
      ":func:`sklearn.datasets.fetch_california_housing` function.\n",
      "\n",
      ".. rubric:: References\n",
      "\n",
      "- Pace, R. Kelley and Ronald Barry, Sparse Spatial Autoregressions,\n",
      "  Statistics and Probability Letters, 33 (1997) 291-297\n",
      "\n",
      "(20640, 8)\n",
      "(20640,)\n"
     ]
    }
   ],
   "execution_count": 3
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-21T13:08:56.955108Z",
     "start_time": "2025-02-21T13:08:56.949407Z"
    }
   },
   "cell_type": "code",
   "source": [
    "print(housing.feature_names)\n",
    "housing.data[0]"
   ],
   "id": "817f9a7dfae07a22",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['MedInc', 'HouseAge', 'AveRooms', 'AveBedrms', 'Population', 'AveOccup', 'Latitude', 'Longitude']\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "array([   8.3252    ,   41.        ,    6.98412698,    1.02380952,\n",
       "        322.        ,    2.55555556,   37.88      , -122.23      ])"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 4
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-21T13:08:56.974839Z",
     "start_time": "2025-02-21T13:08:56.956113Z"
    }
   },
   "cell_type": "code",
   "source": [
    "\n",
    "\n",
    "# 转换为DataFrame\n",
    "df = pd.DataFrame(housing.data, \n",
    "                 columns=housing.feature_names)\n",
    "\n",
    "# 添加目标值（可选）\n",
    "df['MedHouseVal'] = housing.target\n",
    "\n",
    "# 现在可以使用pandas方法\n",
    "print(type(df))\n",
    "print(df.shape)\n",
    "df\n"
   ],
   "id": "defa39e7a676e0e6",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class 'pandas.core.frame.DataFrame'>\n",
      "(20640, 9)\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "       MedInc  HouseAge  AveRooms  AveBedrms  Population  AveOccup  Latitude  \\\n",
       "0      8.3252      41.0  6.984127   1.023810       322.0  2.555556     37.88   \n",
       "1      8.3014      21.0  6.238137   0.971880      2401.0  2.109842     37.86   \n",
       "2      7.2574      52.0  8.288136   1.073446       496.0  2.802260     37.85   \n",
       "3      5.6431      52.0  5.817352   1.073059       558.0  2.547945     37.85   \n",
       "4      3.8462      52.0  6.281853   1.081081       565.0  2.181467     37.85   \n",
       "...       ...       ...       ...        ...         ...       ...       ...   \n",
       "20635  1.5603      25.0  5.045455   1.133333       845.0  2.560606     39.48   \n",
       "20636  2.5568      18.0  6.114035   1.315789       356.0  3.122807     39.49   \n",
       "20637  1.7000      17.0  5.205543   1.120092      1007.0  2.325635     39.43   \n",
       "20638  1.8672      18.0  5.329513   1.171920       741.0  2.123209     39.43   \n",
       "20639  2.3886      16.0  5.254717   1.162264      1387.0  2.616981     39.37   \n",
       "\n",
       "       Longitude  MedHouseVal  \n",
       "0        -122.23        4.526  \n",
       "1        -122.22        3.585  \n",
       "2        -122.24        3.521  \n",
       "3        -122.25        3.413  \n",
       "4        -122.25        3.422  \n",
       "...          ...          ...  \n",
       "20635    -121.09        0.781  \n",
       "20636    -121.21        0.771  \n",
       "20637    -121.22        0.923  \n",
       "20638    -121.32        0.847  \n",
       "20639    -121.24        0.894  \n",
       "\n",
       "[20640 rows x 9 columns]"
      ],
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>MedInc</th>\n",
       "      <th>HouseAge</th>\n",
       "      <th>AveRooms</th>\n",
       "      <th>AveBedrms</th>\n",
       "      <th>Population</th>\n",
       "      <th>AveOccup</th>\n",
       "      <th>Latitude</th>\n",
       "      <th>Longitude</th>\n",
       "      <th>MedHouseVal</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>8.3252</td>\n",
       "      <td>41.0</td>\n",
       "      <td>6.984127</td>\n",
       "      <td>1.023810</td>\n",
       "      <td>322.0</td>\n",
       "      <td>2.555556</td>\n",
       "      <td>37.88</td>\n",
       "      <td>-122.23</td>\n",
       "      <td>4.526</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>8.3014</td>\n",
       "      <td>21.0</td>\n",
       "      <td>6.238137</td>\n",
       "      <td>0.971880</td>\n",
       "      <td>2401.0</td>\n",
       "      <td>2.109842</td>\n",
       "      <td>37.86</td>\n",
       "      <td>-122.22</td>\n",
       "      <td>3.585</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>7.2574</td>\n",
       "      <td>52.0</td>\n",
       "      <td>8.288136</td>\n",
       "      <td>1.073446</td>\n",
       "      <td>496.0</td>\n",
       "      <td>2.802260</td>\n",
       "      <td>37.85</td>\n",
       "      <td>-122.24</td>\n",
       "      <td>3.521</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>5.6431</td>\n",
       "      <td>52.0</td>\n",
       "      <td>5.817352</td>\n",
       "      <td>1.073059</td>\n",
       "      <td>558.0</td>\n",
       "      <td>2.547945</td>\n",
       "      <td>37.85</td>\n",
       "      <td>-122.25</td>\n",
       "      <td>3.413</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>3.8462</td>\n",
       "      <td>52.0</td>\n",
       "      <td>6.281853</td>\n",
       "      <td>1.081081</td>\n",
       "      <td>565.0</td>\n",
       "      <td>2.181467</td>\n",
       "      <td>37.85</td>\n",
       "      <td>-122.25</td>\n",
       "      <td>3.422</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>...</th>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>20635</th>\n",
       "      <td>1.5603</td>\n",
       "      <td>25.0</td>\n",
       "      <td>5.045455</td>\n",
       "      <td>1.133333</td>\n",
       "      <td>845.0</td>\n",
       "      <td>2.560606</td>\n",
       "      <td>39.48</td>\n",
       "      <td>-121.09</td>\n",
       "      <td>0.781</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>20636</th>\n",
       "      <td>2.5568</td>\n",
       "      <td>18.0</td>\n",
       "      <td>6.114035</td>\n",
       "      <td>1.315789</td>\n",
       "      <td>356.0</td>\n",
       "      <td>3.122807</td>\n",
       "      <td>39.49</td>\n",
       "      <td>-121.21</td>\n",
       "      <td>0.771</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>20637</th>\n",
       "      <td>1.7000</td>\n",
       "      <td>17.0</td>\n",
       "      <td>5.205543</td>\n",
       "      <td>1.120092</td>\n",
       "      <td>1007.0</td>\n",
       "      <td>2.325635</td>\n",
       "      <td>39.43</td>\n",
       "      <td>-121.22</td>\n",
       "      <td>0.923</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>20638</th>\n",
       "      <td>1.8672</td>\n",
       "      <td>18.0</td>\n",
       "      <td>5.329513</td>\n",
       "      <td>1.171920</td>\n",
       "      <td>741.0</td>\n",
       "      <td>2.123209</td>\n",
       "      <td>39.43</td>\n",
       "      <td>-121.32</td>\n",
       "      <td>0.847</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>20639</th>\n",
       "      <td>2.3886</td>\n",
       "      <td>16.0</td>\n",
       "      <td>5.254717</td>\n",
       "      <td>1.162264</td>\n",
       "      <td>1387.0</td>\n",
       "      <td>2.616981</td>\n",
       "      <td>39.37</td>\n",
       "      <td>-121.24</td>\n",
       "      <td>0.894</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>20640 rows × 9 columns</p>\n",
       "</div>"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 5
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-21T13:08:56.987540Z",
     "start_time": "2025-02-21T13:08:56.976117Z"
    }
   },
   "cell_type": "code",
   "source": "df.info()  # 查看数据类型和缺失值",
   "id": "40f35ccf7f483a95",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class 'pandas.core.frame.DataFrame'>\n",
      "RangeIndex: 20640 entries, 0 to 20639\n",
      "Data columns (total 9 columns):\n",
      " #   Column       Non-Null Count  Dtype  \n",
      "---  ------       --------------  -----  \n",
      " 0   MedInc       20640 non-null  float64\n",
      " 1   HouseAge     20640 non-null  float64\n",
      " 2   AveRooms     20640 non-null  float64\n",
      " 3   AveBedrms    20640 non-null  float64\n",
      " 4   Population   20640 non-null  float64\n",
      " 5   AveOccup     20640 non-null  float64\n",
      " 6   Latitude     20640 non-null  float64\n",
      " 7   Longitude    20640 non-null  float64\n",
      " 8   MedHouseVal  20640 non-null  float64\n",
      "dtypes: float64(9)\n",
      "memory usage: 1.4 MB\n"
     ]
    }
   ],
   "execution_count": 6
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-21T13:08:56.992929Z",
     "start_time": "2025-02-21T13:08:56.987540Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# print(housing.data[0:5])\n",
    "import pprint  #打印的格式比较 好看\n",
    "\n",
    "pprint.pprint(housing.data[0:2])\n",
    "print('-'*50)\n",
    "pprint.pprint(housing.target[0:2])"
   ],
   "id": "2e518700b0fd82e9",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "array([[ 8.32520000e+00,  4.10000000e+01,  6.98412698e+00,\n",
      "         1.02380952e+00,  3.22000000e+02,  2.55555556e+00,\n",
      "         3.78800000e+01, -1.22230000e+02],\n",
      "       [ 8.30140000e+00,  2.10000000e+01,  6.23813708e+00,\n",
      "         9.71880492e-01,  2.40100000e+03,  2.10984183e+00,\n",
      "         3.78600000e+01, -1.22220000e+02]])\n",
      "--------------------------------------------------\n",
      "array([4.526, 3.585])\n"
     ]
    }
   ],
   "execution_count": 7
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-21T13:08:56.998010Z",
     "start_time": "2025-02-21T13:08:56.993943Z"
    }
   },
   "cell_type": "code",
   "source": [
    "print(housing.data[0:2])\n",
    "print('-'*50)\n",
    "print(housing.target[0:2])"
   ],
   "id": "b7d8262ad351ee4e",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[ 8.32520000e+00  4.10000000e+01  6.98412698e+00  1.02380952e+00\n",
      "   3.22000000e+02  2.55555556e+00  3.78800000e+01 -1.22230000e+02]\n",
      " [ 8.30140000e+00  2.10000000e+01  6.23813708e+00  9.71880492e-01\n",
      "   2.40100000e+03  2.10984183e+00  3.78600000e+01 -1.22220000e+02]]\n",
      "--------------------------------------------------\n",
      "[4.526 3.585]\n"
     ]
    }
   ],
   "execution_count": 8
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-21T13:08:57.022396Z",
     "start_time": "2025-02-21T13:08:56.999017Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "#拆分训练集和测试集，random_state是随机种子,同样的随机数种子，是为了得到同样的随机值\n",
    "x_train_all, x_test, y_train_all, y_test = train_test_split(\n",
    "    housing.data, housing.target, random_state = 7)\n",
    "x_train, x_valid, y_train, y_valid = train_test_split(\n",
    "    x_train_all, y_train_all, random_state = 11)\n",
    "# 训练集\n",
    "print(type(x_train_all))\n",
    "print(x_train.shape, y_train.shape)\n",
    "# 验证集\n",
    "print(x_valid.shape, y_valid.shape)\n",
    "# 测试集\n",
    "print(x_test.shape, y_test.shape)\n",
    "\n",
    "dataset_maps = {\n",
    "    \"train\": [x_train, y_train], #训练集\n",
    "    \"valid\": [x_valid, y_valid], #验证集\n",
    "    \"test\": [x_test, y_test], #测试集\n",
    "} #把3个数据集都放到字典中\n",
    "print(x_train_all.shape)\n",
    "x_train_all"
   ],
   "id": "e527228cb65aa6c7",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class 'numpy.ndarray'>\n",
      "(11610, 8) (11610,)\n",
      "(3870, 8) (3870,)\n",
      "(5160, 8) (5160,)\n",
      "(15480, 8)\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "array([[   1.725     ,   52.        ,    3.38621586, ...,    2.33810143,\n",
       "          37.88      , -122.26      ],\n",
       "       [   3.312     ,   17.        ,    6.45175767, ...,    2.61555722,\n",
       "          33.72      , -116.37      ],\n",
       "       [   1.2469    ,   33.        ,    4.77325581, ...,    3.36627907,\n",
       "          35.37      , -119.01      ],\n",
       "       ...,\n",
       "       [   4.4821    ,   42.        ,    4.45      , ...,    2.775     ,\n",
       "          34.24      , -118.26      ],\n",
       "       [  11.075     ,   38.        ,    7.20467836, ...,    2.39766082,\n",
       "          33.61      , -117.91      ],\n",
       "       [   3.4333    ,   27.        ,    3.44308036, ...,    1.62165179,\n",
       "          37.57      , -122.33      ]])"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 9
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-21T13:08:57.032312Z",
     "start_time": "2025-02-21T13:08:57.023403Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.preprocessing import StandardScaler\n",
    "scaler = StandardScaler() #标准化\n",
    "scaler.fit(x_train) #fit和fit_transform的区别，fit是计算均值和方差，fit_transform是先fit，然后transform"
   ],
   "id": "b904bc9200946e17",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "StandardScaler()"
      ],
      "text/html": [
       "<style>#sk-container-id-1 {\n",
       "  /* Definition of color scheme common for light and dark mode */\n",
       "  --sklearn-color-text: #000;\n",
       "  --sklearn-color-text-muted: #666;\n",
       "  --sklearn-color-line: gray;\n",
       "  /* Definition of color scheme for unfitted estimators */\n",
       "  --sklearn-color-unfitted-level-0: #fff5e6;\n",
       "  --sklearn-color-unfitted-level-1: #f6e4d2;\n",
       "  --sklearn-color-unfitted-level-2: #ffe0b3;\n",
       "  --sklearn-color-unfitted-level-3: chocolate;\n",
       "  /* Definition of color scheme for fitted estimators */\n",
       "  --sklearn-color-fitted-level-0: #f0f8ff;\n",
       "  --sklearn-color-fitted-level-1: #d4ebff;\n",
       "  --sklearn-color-fitted-level-2: #b3dbfd;\n",
       "  --sklearn-color-fitted-level-3: cornflowerblue;\n",
       "\n",
       "  /* Specific color for light theme */\n",
       "  --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, white)));\n",
       "  --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-icon: #696969;\n",
       "\n",
       "  @media (prefers-color-scheme: dark) {\n",
       "    /* Redefinition of color scheme for dark theme */\n",
       "    --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, #111)));\n",
       "    --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-icon: #878787;\n",
       "  }\n",
       "}\n",
       "\n",
       "#sk-container-id-1 {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 pre {\n",
       "  padding: 0;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-hidden--visually {\n",
       "  border: 0;\n",
       "  clip: rect(1px 1px 1px 1px);\n",
       "  clip: rect(1px, 1px, 1px, 1px);\n",
       "  height: 1px;\n",
       "  margin: -1px;\n",
       "  overflow: hidden;\n",
       "  padding: 0;\n",
       "  position: absolute;\n",
       "  width: 1px;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-dashed-wrapped {\n",
       "  border: 1px dashed var(--sklearn-color-line);\n",
       "  margin: 0 0.4em 0.5em 0.4em;\n",
       "  box-sizing: border-box;\n",
       "  padding-bottom: 0.4em;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-container {\n",
       "  /* jupyter's `normalize.less` sets `[hidden] { display: none; }`\n",
       "     but bootstrap.min.css set `[hidden] { display: none !important; }`\n",
       "     so we also need the `!important` here to be able to override the\n",
       "     default hidden behavior on the sphinx rendered scikit-learn.org.\n",
       "     See: https://github.com/scikit-learn/scikit-learn/issues/21755 */\n",
       "  display: inline-block !important;\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-text-repr-fallback {\n",
       "  display: none;\n",
       "}\n",
       "\n",
       "div.sk-parallel-item,\n",
       "div.sk-serial,\n",
       "div.sk-item {\n",
       "  /* draw centered vertical line to link estimators */\n",
       "  background-image: linear-gradient(var(--sklearn-color-text-on-default-background), var(--sklearn-color-text-on-default-background));\n",
       "  background-size: 2px 100%;\n",
       "  background-repeat: no-repeat;\n",
       "  background-position: center center;\n",
       "}\n",
       "\n",
       "/* Parallel-specific style estimator block */\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item::after {\n",
       "  content: \"\";\n",
       "  width: 100%;\n",
       "  border-bottom: 2px solid var(--sklearn-color-text-on-default-background);\n",
       "  flex-grow: 1;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel {\n",
       "  display: flex;\n",
       "  align-items: stretch;\n",
       "  justify-content: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:first-child::after {\n",
       "  align-self: flex-end;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:last-child::after {\n",
       "  align-self: flex-start;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:only-child::after {\n",
       "  width: 0;\n",
       "}\n",
       "\n",
       "/* Serial-specific style estimator block */\n",
       "\n",
       "#sk-container-id-1 div.sk-serial {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "  align-items: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  padding-right: 1em;\n",
       "  padding-left: 1em;\n",
       "}\n",
       "\n",
       "\n",
       "/* Toggleable style: style used for estimator/Pipeline/ColumnTransformer box that is\n",
       "clickable and can be expanded/collapsed.\n",
       "- Pipeline and ColumnTransformer use this feature and define the default style\n",
       "- Estimators will overwrite some part of the style using the `sk-estimator` class\n",
       "*/\n",
       "\n",
       "/* Pipeline and ColumnTransformer style (default) */\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable {\n",
       "  /* Default theme specific background. It is overwritten whether we have a\n",
       "  specific estimator or a Pipeline/ColumnTransformer */\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "/* Toggleable label */\n",
       "#sk-container-id-1 label.sk-toggleable__label {\n",
       "  cursor: pointer;\n",
       "  display: flex;\n",
       "  width: 100%;\n",
       "  margin-bottom: 0;\n",
       "  padding: 0.5em;\n",
       "  box-sizing: border-box;\n",
       "  text-align: center;\n",
       "  align-items: start;\n",
       "  justify-content: space-between;\n",
       "  gap: 0.5em;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 label.sk-toggleable__label .caption {\n",
       "  font-size: 0.6rem;\n",
       "  font-weight: lighter;\n",
       "  color: var(--sklearn-color-text-muted);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 label.sk-toggleable__label-arrow:before {\n",
       "  /* Arrow on the left of the label */\n",
       "  content: \"▸\";\n",
       "  float: left;\n",
       "  margin-right: 0.25em;\n",
       "  color: var(--sklearn-color-icon);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 label.sk-toggleable__label-arrow:hover:before {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "/* Toggleable content - dropdown */\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content {\n",
       "  max-height: 0;\n",
       "  max-width: 0;\n",
       "  overflow: hidden;\n",
       "  text-align: left;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content pre {\n",
       "  margin: 0.2em;\n",
       "  border-radius: 0.25em;\n",
       "  color: var(--sklearn-color-text);\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content.fitted pre {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-toggleable__control:checked~div.sk-toggleable__content {\n",
       "  /* Expand drop-down */\n",
       "  max-height: 200px;\n",
       "  max-width: 100%;\n",
       "  overflow: auto;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-toggleable__control:checked~label.sk-toggleable__label-arrow:before {\n",
       "  content: \"▾\";\n",
       "}\n",
       "\n",
       "/* Pipeline/ColumnTransformer-specific style */\n",
       "\n",
       "#sk-container-id-1 div.sk-label input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator-specific style */\n",
       "\n",
       "/* Colorize estimator box */\n",
       "#sk-container-id-1 div.sk-estimator input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label label.sk-toggleable__label,\n",
       "#sk-container-id-1 div.sk-label label {\n",
       "  /* The background is the default theme color */\n",
       "  color: var(--sklearn-color-text-on-default-background);\n",
       "}\n",
       "\n",
       "/* On hover, darken the color of the background */\n",
       "#sk-container-id-1 div.sk-label:hover label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "/* Label box, darken color on hover, fitted */\n",
       "#sk-container-id-1 div.sk-label.fitted:hover label.sk-toggleable__label.fitted {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator label */\n",
       "\n",
       "#sk-container-id-1 div.sk-label label {\n",
       "  font-family: monospace;\n",
       "  font-weight: bold;\n",
       "  display: inline-block;\n",
       "  line-height: 1.2em;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label-container {\n",
       "  text-align: center;\n",
       "}\n",
       "\n",
       "/* Estimator-specific */\n",
       "#sk-container-id-1 div.sk-estimator {\n",
       "  font-family: monospace;\n",
       "  border: 1px dotted var(--sklearn-color-border-box);\n",
       "  border-radius: 0.25em;\n",
       "  box-sizing: border-box;\n",
       "  margin-bottom: 0.5em;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "/* on hover */\n",
       "#sk-container-id-1 div.sk-estimator:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Specification for estimator info (e.g. \"i\" and \"?\") */\n",
       "\n",
       "/* Common style for \"i\" and \"?\" */\n",
       "\n",
       ".sk-estimator-doc-link,\n",
       "a:link.sk-estimator-doc-link,\n",
       "a:visited.sk-estimator-doc-link {\n",
       "  float: right;\n",
       "  font-size: smaller;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1em;\n",
       "  height: 1em;\n",
       "  width: 1em;\n",
       "  text-decoration: none !important;\n",
       "  margin-left: 0.5em;\n",
       "  text-align: center;\n",
       "  /* unfitted */\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted,\n",
       "a:link.sk-estimator-doc-link.fitted,\n",
       "a:visited.sk-estimator-doc-link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "div.sk-estimator:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "div.sk-estimator.fitted:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "/* Span, style for the box shown on hovering the info icon */\n",
       ".sk-estimator-doc-link span {\n",
       "  display: none;\n",
       "  z-index: 9999;\n",
       "  position: relative;\n",
       "  font-weight: normal;\n",
       "  right: .2ex;\n",
       "  padding: .5ex;\n",
       "  margin: .5ex;\n",
       "  width: min-content;\n",
       "  min-width: 20ex;\n",
       "  max-width: 50ex;\n",
       "  color: var(--sklearn-color-text);\n",
       "  box-shadow: 2pt 2pt 4pt #999;\n",
       "  /* unfitted */\n",
       "  background: var(--sklearn-color-unfitted-level-0);\n",
       "  border: .5pt solid var(--sklearn-color-unfitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted span {\n",
       "  /* fitted */\n",
       "  background: var(--sklearn-color-fitted-level-0);\n",
       "  border: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link:hover span {\n",
       "  display: block;\n",
       "}\n",
       "\n",
       "/* \"?\"-specific style due to the `<a>` HTML tag */\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link {\n",
       "  float: right;\n",
       "  font-size: 1rem;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1rem;\n",
       "  height: 1rem;\n",
       "  width: 1rem;\n",
       "  text-decoration: none;\n",
       "  /* unfitted */\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "#sk-container-id-1 a.estimator_doc_link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "</style><div id=\"sk-container-id-1\" class=\"sk-top-container\"><div class=\"sk-text-repr-fallback\"><pre>StandardScaler()</pre><b>In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. <br />On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.</b></div><div class=\"sk-container\" hidden><div class=\"sk-item\"><div class=\"sk-estimator fitted sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-1\" type=\"checkbox\" checked><label for=\"sk-estimator-id-1\" class=\"sk-toggleable__label fitted sk-toggleable__label-arrow\"><div><div>StandardScaler</div></div><div><a class=\"sk-estimator-doc-link fitted\" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.6/modules/generated/sklearn.preprocessing.StandardScaler.html\">?<span>Documentation for StandardScaler</span></a><span class=\"sk-estimator-doc-link fitted\">i<span>Fitted</span></span></div></label><div class=\"sk-toggleable__content fitted\"><pre>StandardScaler()</pre></div> </div></div></div></div>"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 10
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-21T13:08:57.038845Z",
     "start_time": "2025-02-21T13:08:57.033791Z"
    }
   },
   "cell_type": "code",
   "source": "np.array(1).shape",
   "id": "e6d6d65c12557825",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "()"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 11
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "### 构建数据集",
   "id": "ffa303319345dade"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-21T13:08:57.051141Z",
     "start_time": "2025-02-21T13:08:57.039884Z"
    }
   },
   "cell_type": "code",
   "source": [
    "#构建私有数据集，就需要用如下方式\n",
    "from torch.utils.data import Dataset\n",
    "\n",
    "class HousingDataset(Dataset):\n",
    "    def __init__(self, mode='train'):\n",
    "        self.x, self.y = dataset_maps[mode] #x,y都是ndarray类型\n",
    "        self.x = torch.from_numpy(scaler.transform(self.x)).float() #from_numpy将NumPy数组转换成PyTorch张量\n",
    "        self.y = torch.from_numpy(self.y).float().reshape(-1, 1) #处理为多行1列的tensor类型,因为__getitem__切片时需要\n",
    "            \n",
    "    def __len__(self): #必须写\n",
    "        return len(self.x) #返回数据集的长度,0维的size\n",
    "    \n",
    "    def __getitem__(self, idx): #idx是索引，返回的是数据和标签，这里是一个样本\n",
    "        return self.x[idx], self.y[idx]\n",
    "    \n",
    "#train_ds是dataset类型的数据，与前面例子的FashionMNIST类型一致\n",
    "train_ds = HousingDataset(\"train\")\n",
    "valid_ds = HousingDataset(\"valid\")\n",
    "test_ds = HousingDataset(\"test\")"
   ],
   "id": "1f63484ee13dfb78",
   "outputs": [],
   "execution_count": 12
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-21T13:08:57.057522Z",
     "start_time": "2025-02-21T13:08:57.052145Z"
    }
   },
   "cell_type": "code",
   "source": "type(train_ds)",
   "id": "86de221414289b12",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "__main__.HousingDataset"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 13
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-21T13:08:57.067506Z",
     "start_time": "2025-02-21T13:08:57.059037Z"
    }
   },
   "cell_type": "code",
   "source": "train_ds[0]",
   "id": "910567ddae396e53",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(tensor([ 0.8015,  0.2722, -0.1162, -0.2023, -0.5431, -0.0210, -0.5898, -0.0824]),\n",
       " tensor([3.2260]))"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 14
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-21T13:08:57.074593Z",
     "start_time": "2025-02-21T13:08:57.069016Z"
    }
   },
   "cell_type": "code",
   "source": "train_ds[0][0]",
   "id": "2dd55d0a597b24fb",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([ 0.8015,  0.2722, -0.1162, -0.2023, -0.5431, -0.0210, -0.5898, -0.0824])"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 15
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-21T13:08:57.081109Z",
     "start_time": "2025-02-21T13:08:57.075597Z"
    }
   },
   "cell_type": "code",
   "source": "train_ds[0][1]",
   "id": "1a85228b09fce2f4",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([3.2260])"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 16
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "### DataLoader",
   "id": "38063dbbf0c4b86a"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-21T13:08:57.086977Z",
     "start_time": "2025-02-21T13:08:57.082114Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from torch.utils.data import DataLoader\n",
    "\n",
    "#放到DataLoader中的train_ds, valid_ds, test_ds都是dataset类型的数据\n",
    "batch_size = 16 #batch_size是可以调的超参数\n",
    "train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True)\n",
    "val_loader = DataLoader(valid_ds, batch_size=batch_size, shuffle=False)\n",
    "test_loader = DataLoader(test_ds, batch_size=batch_size, shuffle=False)"
   ],
   "id": "a0cacf04d4a1f9c0",
   "outputs": [],
   "execution_count": 17
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 定义模型",
   "id": "24bc1db66f38542c"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-21T13:08:57.092825Z",
     "start_time": "2025-02-21T13:08:57.088229Z"
    }
   },
   "cell_type": "code",
   "source": [
    "#回归模型我们只需要1个数\n",
    "\n",
    "class NeuralNetwork(nn.Module):\n",
    "    def __init__(self, input_dim=8):\n",
    "        super().__init__()\n",
    "        self.linear_relu_stack = nn.Sequential(\n",
    "            nn.Linear(input_dim, 30),\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(30, 1)\n",
    "            )\n",
    "        \n",
    "    def forward(self, x):\n",
    "        # x.shape [batch size, 8]\n",
    "        logits = self.linear_relu_stack(x)\n",
    "        # logits.shape [batch size, 1]\n",
    "        return logits"
   ],
   "id": "e8953a27f06516f4",
   "outputs": [],
   "execution_count": 18
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-21T13:08:57.099338Z",
     "start_time": "2025-02-21T13:08:57.093830Z"
    }
   },
   "cell_type": "code",
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        \"\"\"\n",
    "\n",
    "        Args:\n",
    "            patience (int, optional): Number of epochs with no improvement after which training will be stopped.. Defaults to 5.\n",
    "            min_delta (float, optional): Minimum change in the monitored quantity to qualify as an improvement, i.e. an absolute \n",
    "                change of less than min_delta, will count as no improvement. Defaults to 0.01.\n",
    "        \"\"\"\n",
    "        self.patience = patience\n",
    "        self.min_delta = min_delta\n",
    "        self.best_metric = -1\n",
    "        self.counter = 0\n",
    "        \n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "            # update best metric\n",
    "            self.best_metric = metric\n",
    "            # reset counter \n",
    "            self.counter = 0\n",
    "        else: \n",
    "            self.counter += 1\n",
    "            \n",
    "    @property\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience\n"
   ],
   "id": "90ddea3809adffa1",
   "outputs": [],
   "execution_count": 19
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-21T13:08:57.105034Z",
     "start_time": "2025-02-21T13:08:57.100863Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "@torch.no_grad()\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []\n",
    "    for datas, labels in dataloader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "        # 前向计算\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)         # 验证集损失\n",
    "        loss_list.append(loss.item())\n",
    "        \n",
    "    return np.mean(loss_list)\n"
   ],
   "id": "22a588d86c86b4fc",
   "outputs": [],
   "execution_count": 20
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-21T13:17:55.397173Z",
     "start_time": "2025-02-21T13:15:12.641792Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 训练\n",
    "def training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer,\n",
    "    early_stop_callback=None,\n",
    "    eval_step=500,\n",
    "    ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "    \n",
    "    global_step = 0\n",
    "    model.train()\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for datas, labels in train_loader:#11610/16=726\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "                # 模型前向计算\n",
    "                logits = model(datas) #得到预测值\n",
    "                # 计算损失\n",
    "                loss = loss_fct(logits, labels)\n",
    "                # 梯度回传\n",
    "                loss.backward()\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    " \n",
    "                loss = loss.cpu().item() #转为cpu类型，item()是取值\n",
    "                # record\n",
    "                \n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"step\": global_step\n",
    "                })\n",
    "                \n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()\n",
    "                    val_loss = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"step\": global_step\n",
    "                    })\n",
    "                    model.train()\n",
    "\n",
    "                    # 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(-val_loss) #根据验证集的损失来实现早停\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "                    \n",
    "                # udate step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "        \n",
    "    return record_dict\n",
    "        \n",
    "\n",
    "epoch = 100\n",
    "\n",
    "model = NeuralNetwork()\n",
    "\n",
    "# 1. 定义损失函数 采用MSE损失,均方误差\n",
    "loss_fct = nn.MSELoss()\n",
    "# 2. 定义优化器 采用SGD\n",
    "# Optimizers specified in the torch.optim package\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9)\n",
    "\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=10, min_delta=1e-3)\n",
    "\n",
    "model = model.to(device)\n",
    "record = training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_loader)\n",
    "    )"
   ],
   "id": "7c72a2c65b65b448",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "  0%|          | 0/72600 [00:00<?, ?it/s]"
      ],
      "application/vnd.jupyter.widget-view+json": {
       "version_major": 2,
       "version_minor": 0,
       "model_id": "5b6242faceb14a11a64a5e8632b104d0"
      }
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Early stop at epoch 77 / global_step 55902\n"
     ]
    }
   ],
   "execution_count": 22
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-22T03:37:42.148555Z",
     "start_time": "2025-02-22T03:37:42.025372Z"
    }
   },
   "cell_type": "code",
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "    # print(train_df)\n",
    "    print(train_df.columns)\n",
    "    print(val_df)\n",
    "    # plot\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        plt.plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        plt.plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        plt.grid()\n",
    "        plt.legend()\n",
    "        # plt.xticks(range(0, train_df.index[-1], 10*sample_step), range(0, train_df.index[-1], 10*sample_step))\n",
    "        plt.xlabel(\"step\")\n",
    "\n",
    "        plt.show()\n",
    "\n",
    "plot_learning_curves(record)  #横坐标是 steps"
   ],
   "id": "53bb7f25a33a99a",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Index(['loss'], dtype='object')\n",
      "           loss\n",
      "step           \n",
      "0      6.374675\n",
      "726    0.528643\n",
      "1452   0.413891\n",
      "2178   0.409725\n",
      "2904   0.399230\n",
      "...         ...\n",
      "52998  0.319276\n",
      "53724  0.318488\n",
      "54450  0.315028\n",
      "55176  0.318678\n",
      "55902  0.311879\n",
      "\n",
      "[78 rows x 1 columns]\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ],
      "image/png": "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PYKMi5bt2bMHiYmJcDgcQXtcazn2nTt3BnU59ppEbRgYtV/g1IaBUxsGRu1XOoYLDCqaNWuGiIiIqpOx4Ma0aNGi0h6fO4J2hsCoDQOj9guc2jBwasPAqP1KVlamwqLiTREREQkaBRYiIiISNLYJLGJjY/H444+br3Jy1IaBUfsFTm0YOLVhYNR+gQt58aaIiIjYl20yFiIiIhJ+CixEREQkaBRYiIiISNAosBAREZGgsU1g8corr6B169aIi4vD6aefjoULF8Lu5syZg4suusjMgsZZTL/++utCf2dd7mOPPYamTZsiPj4eQ4YMwcaNGwvd5/Dhwxg5cqSZCKZOnTq4+eabkZmZWeg+K1aswFlnnWXaljPSPffcc8W25fPPP0enTp3MfU499VRMnjwZ1cG4cePQr18/MxNso0aNcOmll2L9+vWF7pOTk4NRo0ahfv36qF27Nq644grs37+/0H127NiBCy+8EAkJCeZxHnzwQRQUFBS6z6xZs9C7d29Tbd6+fXu8++671X4/fu2119C9e3ffZEL9+/fHlClTfH9X21Xcs88+az7P9913n+93aseyjRkzxrSZ/43HI4vaL8TcNvDpp5+6Y2Ji3O+884579erV7ltvvdVdp04d9/79+912NnnyZPff/vY391dffcWRPe5JkyYV+vuzzz7rTk5Odn/99dfu5cuXuy+++GJ3mzZt3MeOHfPd54ILLnD36NHDPX/+fPfcuXPd7du3d19zzTW+v6elpbkbN27sHjlypHvVqlXuTz75xB0fH+9+4403fPf55Zdf3JGRke7nnnvOvWbNGvff//53d3R0tHvlypXuqu788893T5gwwby2ZcuWuUeMGOFu2bKlOzMz03ef22+/3Z2SkuKeMWOGe/Hixe4zzjjDfeaZZ/r+XlBQ4O7WrZt7yJAh7t9++828Lw0aNHCPHj3ad58tW7a4ExIS3Pfff79po5deesm02dSpU6v1fvztt9+6v//+e/eGDRvc69evdz/yyCPmvWd7ktquYhYuXOhu3bq1u3v37u57773X93u1Y9kef/xxd9euXd179+713Q4ePOj7u9ovtGwRWJx22mnuUaNG+X52Op3uZs2auceNG+euKYoGFi6Xy92kSRP3888/7/vd0aNH3bGxsSY4IH44+H+LFi3y3WfKlCluh8Ph3r17t/n51VdfddetW9ed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"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 29
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-22T03:32:52.133323Z",
     "start_time": "2025-02-22T03:32:51.440482Z"
    }
   },
   "cell_type": "code",
   "source": [
    "model.eval()\n",
    "loss = evaluating(model, test_loader, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\")"
   ],
   "id": "537fde370587ee50",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss:     0.3140\n"
     ]
    }
   ],
   "execution_count": 24
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
